#include "LogWriter.h"
#include "ErrorInfo.h"
#include <iostream>
#include <sys/file.h>

LogWriter::LogWriter() {

}

LogWriter::~LogWriter() {
    if (msync(recordPtr, static_cast<size_t>(logPageSize), MS_SYNC) == -1) {
        showLog("工具退出异常,%s","数据同步磁盘失败");
    }

    if (munmap(recordPtr, static_cast<size_t>(logPageSize)) == -1) {
        close(fd);
        showLog("工具退出异常,%s","munmap失败");
        exit(EXIT_FAILURE);
    }
    close(fd);
    // shrink_to_fit ,释放多余的空间
    filePath.shrink_to_fit();
}


ErrorInfo *
LogWriter::init(JNIEnv *env,   std::string mLogDir ) {
    // std::move 将对象的状态或者所有权从一个对象转移到另一个对象，只是转移，没有内存的搬迁或者内存拷贝
    return initMmap(std::move(mLogDir));
}


ErrorInfo *LogWriter::initMmap(std::string mLogDir) {
    std::string buildDate = getDate();
    this->filePath = mLogDir + "/" + buildDate + "-mmap.txt";

    /*
     * O_RDWR: 表示打开文件时可以进行读写操作。
     * O_CREAT：若文件不存在则创建文件
     * O_CLOEXEC:在执行完文件描述符相关的操作后立即关闭它，避免子进程继承该文件描述符。
     * S_IRWXU:权限位，表示文件所有者具有读、写和执行权限，而其他用户只有读和执行权限
     */
    this->fd = open(filePath.c_str(), O_RDWR | O_CREAT, S_IRWXU);

    if (fd == -1) {
        return new ErrorInfo(FAILED, "创建或打开文件失败");
    }

    this->fileStat.st_size = 0;
    // fstat 获取文件状态，成功返回0，失败返回－1
    if (fstat(fd, &fileStat) == -1) {
        close(fd);
        return new ErrorInfo(FAILED, "获取文件状态失败");
    }

    this->logPageSize = static_cast<off_t >(ALLOC_PAGE_NUM * sysconf(_SC_PAGE_SIZE));
    this->fileSize = fileStat.st_size;

    // 如果文件大小不是日志页面大小的整数倍，则将其补充为日志页面大小的整数倍
    bool needTruncateFile =  fileSize < logPageSize || fileSize % logPageSize != 0 ;
    showLog("是否需要调整文件大小=%s", needTruncateFile ? "需要" : "不需要");

    if (needTruncateFile) {
        off_t increaseSize = logPageSize - (fileSize % logPageSize);

        // 文件大小改为指定的大小
        if (ftruncate(fd, fileSize + increaseSize) == -1) {
            close(fd);
            return new ErrorInfo(FAILED, "使用ftruncate调整日志文件大小失败。");
        }

        fileSize += increaseSize;

        if (lseek(fd, fileSize - 1, SEEK_SET) == -1) {
            close(fd);
            return new ErrorInfo(FAILED, "lseek偏移文件失败");
        }

        if (write(fd, "|", sizeof(char)) == -1) {
            close(fd);
            return new ErrorInfo(FAILED, "写入文件最后一个字符失败");
        }
    }

    void *map = mmap(nullptr,
                     static_cast<size_t>(logPageSize),
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd,
                     fileSize - logPageSize);

    if (map == MAP_FAILED || map == nullptr) {
        close(fd);
        return new ErrorInfo(FAILED, "文件开启mmap失败");
    }

    recordPtr = static_cast<char *> (map);

    ErrorInfo *errInfo = checkMmapFile(this);
    if (errInfo != nullptr) {
        syncDataAndUnMmapFile(static_cast<void *>(recordPtr), logPageSize);
        close(fd);
        return errInfo;
    }


    bool findFlag = false;
    for (off_t i = logPageSize - 1; i >= 0; i--) {
        // Find the first '\n' and stop the search,
        // if not found, then the page is still blank, just back to the beginning of the page
        if (recordPtr[i] == '\n') {
            findFlag = true;
            if (i != logPageSize - 1) {
                recordIndex = i + 1;
            } else {
                recordIndex = logPageSize;
            }
            break;
        }
    }
    if (!findFlag) {
        recordIndex = 0;
    }

    memset(recordPtr + recordIndex, 0, static_cast<size_t>(logPageSize - recordIndex));
    return nullptr;
}


ErrorInfo *LogWriter::writeLog(JNIEnv *env, const char *logMsg ) {
    const size_t textSize = strlen(logMsg);
    if (logMsg == nullptr || textSize <= 0) {
        return new ErrorInfo(FAILED, "内容为空");
    }

    if (recordPtr == nullptr) {
        close(fd);
        return new ErrorInfo(FAILED, "recordPtr为空，写入失败");
    }

    ErrorInfo *errInfo = checkMmapFile(this);
    if (errInfo != nullptr) {
        syncDataAndUnMmapFile( static_cast<void *>(recordPtr), logPageSize);
        close(fd);
        return errInfo;
    }

    size_t msgIndex = 0;
    // 确保能把当前内容全部存完
    while (true) {
        for (; msgIndex < textSize && recordIndex < logPageSize; msgIndex++) {
            recordPtr[recordIndex] = logMsg[msgIndex];
            recordIndex++;
        }

        //当开辟的mmap内存被写满时,需要再开辟一页mmap内存
        if (recordIndex >= logPageSize) {
            showErrorLog("触发扩容");

            ErrorInfo *errInfo = syncDataAndUnMmapFile(  recordPtr, (size_t) logPageSize);
            if (errInfo != nullptr) {
                close(fd);
                return errInfo;
            }

            recordPtr = nullptr;

            if (access(filePath.c_str(), 0) != 0) {
                close(fd);
                return new ErrorInfo(FAILED, "没有写入权限");
            }

            //扩展文件大小
            if (ftruncate(fd, fileSize + logPageSize) == -1) {
                close(fd);
                return new ErrorInfo(FAILED, "使用ftruncate调整日志文件大小失败。2");
            }

            //移动到文件末尾
            if (lseek(fd, fileSize + logPageSize - 1, SEEK_SET) == -1) {
                close(fd);
                return new ErrorInfo(FAILED, "Error calling lseek() to stretch the file");
            }

            //在文件末尾写入一个字符，达到扩展文件大小的目的
            if (write(fd, "", 1) == -1) {
                close(fd);
                return new ErrorInfo(FAILED, "Error writing last byte of the file");
            }

            this->fileStat.st_size = 0;

            if (fstat(fd, &fileStat) == -1) {
                close(fd);
                return new ErrorInfo(FAILED, "Error fstat file");
            }

            if (fileStat.st_size - logPageSize != this->fileSize &&
                fileStat.st_size % logPageSize != 0) {
                close(fd);
                return new ErrorInfo(FAILED, "Error stretch file when writing");
            }

            this->fileSize = fileStat.st_size;

            void *map = mmap(nullptr, static_cast<size_t>(logPageSize), PROT_READ | PROT_WRITE,
                             MAP_SHARED, fd,
                             fileSize - logPageSize);

            if (map == MAP_FAILED || map == nullptr) {
                close(fd);
                return new ErrorInfo(FAILED, "Error mmaping the file");
            }

            recordPtr = static_cast<char *> (map);
            memset(recordPtr, 0, static_cast<size_t >(logPageSize));

            recordIndex = 0;
        } else {
            break;
        }
    }

    return nullptr;
}


std::string LogWriter::getDate() {
    time_t now = time(nullptr);
    tm localTime = *localtime(&now);
    std::string *date;
    size_t bufSize = sizeof(char) * 20;
    char *buf = (char *) malloc(bufSize);
    strftime(buf, bufSize, "%Y-%m-%d", &localTime);
    date = new std::string(buf);
    free(buf);
    return *date;
}

ErrorInfo *LogWriter::checkMmapFile(LogWriter* mWriter) {
    // access 有做某事的权限
    if (access(mWriter->filePath.c_str(), 0) != 0) {
        return new ErrorInfo(FAILED, "没有权限访问日志文件");
    }
    // fstat 获取文件状态，成功返回0，失败返回－1
    mWriter->fileStat.st_size = 0;
    if (fstat(mWriter->fd, &mWriter->fileStat) == -1 || mWriter->fileStat.st_size != mWriter->fileSize) {
        return new ErrorInfo(FAILED, "获取文件状态失败");
    }
    return nullptr;
}


/**
 * 将内存中的数据同步到文件中，并解除映射
 */
ErrorInfo *LogWriter::syncDataAndUnMmapFile(  void *map, size_t map_size) {
    // 修改写回到被映射的文件中
    if (msync(map, map_size, MS_SYNC) == -1) {
        return new ErrorInfo(FAILED, "内容同步到文件，失败！");
    }
    if (munmap(map, map_size) == -1) {
        return new ErrorInfo(FAILED, "文件进行munmap失败");
    }
    return nullptr;
}

